//-----------------------------------------------------------------------
// <copyright file="ACLTest.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Simon Bell</author>
// <email>v-sibell</email>
// <date>2004-05-28</date>
// <summary>Tests ACL createion and removal</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Test
{
    using System;
    using System.IO;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Microsoft.Sdc.Tasks.Configuration;
    using Microsoft.Sdc.Tasks;
    using Microsoft.Sdc.Tasks.ActiveDirectory;
    using Microsoft.Sdc.Tasks.Configuration.ActiveDirectory;
    using Microsoft.Build.Framework;

    /// <summary>
    /// Unit test for the ACL tasks
    /// </summary>
    [TestClass]
    public class ACLTest
    {
        
        private string DEFAULT_DOMAIN = Environment.MachineName;
        private string sourceFolderName;
        private string subFolderName;
        private string[] sourceFiles;
        private string[] subFiles;

        // Access mask (see AccessMask property)
        [Flags]
        enum Mask : uint
        {
            FileReadData = 0x00000001,
            FileWriteData = 0x00000002,
            FileAppendData = 0x00000004,
            FileReadEA = 0x00000008,
            FileWriteEA = 0x00000010,
            FileExecute = 0x00000020,
            FileDeleteChild = 0x00000040,
            FileReadAttributes = 0x00000080,
            FileWriteAttributes = 0x00000100,

            Delete = 0x00010000,
            ReadControl = 0x00020000,
            WriteDac = 0x00040000,
            WriteOwner = 0x00080000,
            Synchronize = 0x00100000,

            AccessSystemSecurity = 0x01000000,
            MaximumAllowed = 0x02000000,

            GenericAll = 0x10000000,
            GenericExecute = 0x20000000,
            GenericWrite = 0x40000000,
            GenericRead = 0x80000000
        }

        public ACLTest()
        {
        }

       

        [TestMethod]
        public void TestSuccess()
        {
            //Create a temporary user
            Tasks.ActiveDirectory.User.Create newUser = CreateTemporaryUser();

            //Create temporary files and folders
            CreateFoldersAndFiles();

            try
            {
                //Give the user access to the source folder only
                Tasks.Security.AddAcl addAclTask = new Tasks.Security.AddAcl();
                
                string userOrGroup = DEFAULT_DOMAIN + "\\" + newUser.UserName;
                addAclTask.UserOrGroup = userOrGroup;
                addAclTask.Path = sourceFolderName;
                addAclTask.AccessType = "ReadAttributes";
                addAclTask.ApplyTo = "ThisFolder";

                bool addAclTaskReturnValue = addAclTask.Execute();

                //Test the ACL was created
                Assert.IsTrue( addAclTaskReturnValue);

                //Ensure this is correct
                bool aceExists = false;

                //Check the acl for the new addition
                aceExists = CheckAcl(addAclTask.Path, newUser.UserName, DEFAULT_DOMAIN);
                Assert.IsTrue( aceExists);

                //Test the properties of the task
                Assert.IsTrue( addAclTask.UserOrGroup == userOrGroup);
                Assert.IsTrue( addAclTask.Path == sourceFolderName);
                Assert.IsTrue( addAclTask.AccessType == "ReadAttributes");
                Assert.IsTrue( addAclTask.ApplyTo == "ThisFolder");

                //NB (S. Bell)
                //###
                //Calls to CheckAcl, whilst successful, have been found to result in a 
                //test that is too slow for the build server. Hence we are now just checking
                //for .Execute() method success

                //Test that the new user does NOT have an ACE for the subfolder
                //aceExists = CheckAcl(subFolderName, newUser.UserName, DEFAULT_DOMAIN);
                //Assert.IsTrue("NoAclOnSubFolderProved", !aceExists);
                //###

                //Give the user permission on the sub-folder
                addAclTask.Path = subFolderName;
                addAclTask.ApplyTo = "ThisFolder";
                addAclTaskReturnValue = addAclTask.Execute();

                //NOTE - I have to use the ThisFolder | subFolderName combination
                //to get this to work. Using SubFolders (note the plural...) with sourceFolderName
                //returns true but fails the CheckAcl test.

                //Test the ACL was created
                Assert.IsTrue( addAclTaskReturnValue);

                //###
                //Test that the new user now DOES have an ACE for the subfolder
                //aceExists = CheckAcl(subFolderName, newUser.UserName, DEFAULT_DOMAIN);
                //Assert.IsTrue("AclOnSubFolderProved", aceExists);
                //###

                //Now remove the ACLs
                Tasks.Security.RemoveAcl removeAclTask = new Tasks.Security.RemoveAcl();
                removeAclTask.UserOrGroup = userOrGroup;
                removeAclTask.Path = sourceFolderName;

                bool removeAclTaskReturnValue = removeAclTask.Execute();

                //Test the ACL was removed
                Assert.IsTrue( removeAclTaskReturnValue);

                //Test the properties of the task
                Assert.IsTrue( removeAclTask.UserOrGroup == userOrGroup);
                Assert.IsTrue( removeAclTask.Path == sourceFolderName);

                //###
                //aceExists = CheckAcl(removeAclTask.Path, newUser.UserName, DEFAULT_DOMAIN);
                //Assert.IsTrue("AclProvedRemoved", !aceExists);
                //###

                removeAclTask.Path = subFolderName;

                removeAclTaskReturnValue = removeAclTask.Execute();

                //###
                //aceExists = CheckAcl(removeAclTask.Path, newUser.UserName, DEFAULT_DOMAIN);
                //Assert.IsTrue("AclProvedRemoved", !aceExists);
                //###


            }
            finally
            {
                //Now delete the user
                User user = User.Load(newUser.UserName);
                bool userDeleted = user.Delete();
                Assert.IsTrue( userDeleted);

                //And delete the temporary files
                DestroyFilesAndFolders();
            }
        }

        [TestMethod]
        public void TestUninitializedAddAclTaskFails()
        {
            Tasks.Security.AddAcl task = new Tasks.Security.AddAcl();
            
            try
            {
                bool taskReturnValue = task.Execute();
                Assert.Fail("Uninitialised tasks should not succeed.");
            }
            catch (ApplicationException)
            {
                Console.WriteLine("Correctly threw exception");
                Console.WriteLine(((PseudoBuildEngine) task.BuildEngine).PreviousMessage);
            }

        }

        [TestMethod]
        public void TestUninitializedRemoveAclTaskFails()
        {
            Tasks.Security.RemoveAcl task = new Tasks.Security.RemoveAcl();
            
            try
            {
                bool taskReturnValue = task.Execute();
                Assert.Fail("Uninitialised tasks should not succeed.");
            }
            catch (ApplicationException)
            {
                Console.WriteLine("Correctly threw exception");
                Console.WriteLine(((PseudoBuildEngine) task.BuildEngine).PreviousMessage);
            }
        }


        private Microsoft.Sdc.Tasks.ActiveDirectory.User.Create CreateTemporaryUser()
        {
            Microsoft.Sdc.Tasks.ActiveDirectory.User.Create task = new Tasks.ActiveDirectory.User.Create();
           

            //Generate a new random username
            Guid guid = Guid.NewGuid();
            string username = guid.ToString();
            username = username.Replace("-", "").Substring(0, 11);

            if (User.Exists(username))
            {
                Assert.Fail("User already existed");
            }

            task.UserName = username;
            task.Password = "1234$abcd";
            bool taskReturnValue = task.Execute();
            Assert.IsTrue(taskReturnValue);

            //Check the user now exists
            bool userExists = User.Exists(username);
            Console.WriteLine("Created user username = " + username);
            Assert.IsTrue(userExists);

            return task;
        }

        private void CreateFoldersAndFiles()
        {
            //Create a source folder. 
            sourceFolderName = TaskTestUtilities.GenerateTempDirectoryPath();

            // Ensure it doesn't exist
            if (Directory.Exists(sourceFolderName))
            {
                Assert.Fail("Temp directory already existed");
            }

            //Create the folder
            Directory.CreateDirectory(sourceFolderName);

            //Create a sub folder.
            subFolderName = TaskTestUtilities.GenerateNewGuid();
            subFolderName = sourceFolderName + "\\" + subFolderName;
            Directory.CreateDirectory(subFolderName);

            //Create two files in both folders.
            sourceFiles = new string[2];
            subFiles = new string[2];
            for (int i = 0; i < 2; i++)
            {
                sourceFiles[i] = TaskTestUtilities.CreateTempFileInFolder(sourceFolderName);
                subFiles[i] = TaskTestUtilities.CreateTempFileInFolder(subFolderName);
            }
        }

        private bool CheckAcl(string filename, string userOrGroup, string domain)
        {
            //Generate a system.management query to the requested file 
            System.Management.ManagementObject aclFileQuery = new System.Management.ManagementObject(@"Win32_LogicalFileSecuritySetting.Path=" + "'" + filename + "'");
            bool retValue = false;
            using (aclFileQuery)
            {
                // Get the security descriptor for this object and find the requested trustee
                System.Management.ManagementBaseObject outParams = aclFileQuery.InvokeMethod("GetSecurityDescriptor", null, null);
                if (((uint) (outParams.Properties["ReturnValue"].Value)) == 0) // if success
                {
                    System.Management.ManagementBaseObject securityDescriptor = ((System.Management.ManagementBaseObject) (outParams.Properties["Descriptor"].Value));

                    //The DACL is an array of Win32_ACE objects.
                    System.Management.ManagementBaseObject[] dacl = ((System.Management.ManagementBaseObject[]) (securityDescriptor.Properties["Dacl"].Value));
                    retValue = CheckACEs(dacl, userOrGroup, domain, Mask.FileReadAttributes);
                }
            }
            return retValue;
        }

        private bool CheckACEs(System.Management.ManagementBaseObject[] dacl, string userOrGroupName, string domain, Mask attributeToCheck)
        {
            bool validAce = false;

            foreach (System.Management.ManagementBaseObject ace in dacl)
            {
                //Get the trustee we're looking for
                System.Management.ManagementBaseObject currentTrustee = ((System.Management.ManagementBaseObject) (ace["Trustee"]));
                if ((currentTrustee.Properties["Name"].Value.ToString() == userOrGroupName) && (currentTrustee.Properties["Domain"].Value.ToString() == domain))
                {
                    UInt32 accessMask = (UInt32) ace["AccessMask"];
                    if (((Mask) accessMask & attributeToCheck) == attributeToCheck)
                    {
                        return true;
                    }
                }
            }
            return validAce;
        }


        private void DestroyFilesAndFolders()
        {
            //Clean up
            Directory.Delete(sourceFolderName, true);
        }
    }
}


